/*
 * Created by JFormDesigner on Wed Oct 12 12:34:58 CST 2022
 */

package pers.tetris;

import com.sun.awt.AWTUtilities;
import pers.tetris.tools.ImageUtil;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.font.TextAttribute;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.text.AttributedString;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;

/**
 * 主界面
 */
public class MainFrame extends JFrame {
    static Logger logger = Logger.getLogger(MainFrame.class.getName());
    /**
     * 从resources目录下 X_*.jpg 挑出来的几张图片的编号
     */
    private final int[] CHOOSED = {0, 2, 4, 6, 8, 9, 10};
    /**
     * 地图区域 左上角的坐标 横向偏移量
     */
    private final int LEFT_TOP_X = 200;
    /**
     * 地图区域 左上角的坐标 纵向偏移量
     */
    private final int LEFT_TOP_Y = 22;
    /**
     * 地图区域每个方格的大小
     */
    private final int BLOCK_SIZE = 31;
    /**
     * 透明的颜色，用于paint() 时作为背景色，但不知道为什么有时候不起作用
     */
    private final Color TRANSPARENT = new Color(0, 0, 0, 0);
    /**
     * 模型
     */
    TetrisModel model;
    /**
     * 线程池，用于定时任务创建
     */
    ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(3);
    /**
     * 背景图片
     */
    private Image backImage;
    /**
     * 游戏结束的图片
     */
    private Image gameOverImage;
    /**
     * 游戏暂停的图片
     */
    private Image stoppedImage;
    /**
     * 地图 空格 的图片
     */
    private Image empty;
    /**
     * 不同颜色的方格
     */
    private BufferedImage[] blocks = new BufferedImage[7];
    /**
     * 亮化处理的方格，目前没有用到
     */
    private Image[] lightBlocks = new Image[7];
    private Image[] allBlocks = new Image[15];
    private SettingDialog settingDialog;

    /**
     * 右键菜单
     */
    private JPopupMenu popupMenu;
    private JMenuItem menuItemStartGame;
    private JMenuItem menuItemSetting;
    private JMenuItem menuItemExit;
    /**
     * 用于按住左右下按键不放时的定时任务处理
     */
    private AtomicBoolean keyHold = new AtomicBoolean(false);
    /**
     * 用于按住左右下按键不放时的定时任务处理
     */
    private AtomicInteger taskCode = new AtomicInteger();
    /**
     * 用于方块定时下落时的处理
     */
    private AtomicInteger tickCode = new AtomicInteger();
    /**
     * 初始化并加载资源
     *
     * @throws IOException
     */
    public MainFrame() throws IOException {
        ShapeMaker shapeMaker = ShapeMaker.getInstance();
        model = new TetrisModel(shapeMaker::nextShape);

        // 加载各图片
        backImage = ImageIO.read(this.getClass().getClassLoader().getResourceAsStream("frame2.jpg"));
        gameOverImage = ImageIO.read(this.getClass().getClassLoader().getResourceAsStream("game_over2.jpg"));
        stoppedImage = ImageIO.read(this.getClass().getClassLoader().getResourceAsStream("stoped.jpg"));
        empty = ImageIO.read(this.getClass().getClassLoader().getResourceAsStream("gray4.jpg"));
        for (int i = 0; i < 7; i++) {
            blocks[i] = ImageIO.read(this.getClass().getClassLoader().getResourceAsStream("X_" + i + ".png"));
            lightBlocks[i] = ImageUtil.lighter(blocks[i]);
        }
        allBlocks[0] = empty;
        System.arraycopy(blocks, 0, allBlocks, 1, 7);
        System.arraycopy(lightBlocks, 0, allBlocks, 8, 7);

        initComponents();
    }

    /**
     * 创建定时任务：处理左右下按住放的情况
     * @param runnable 处理方式
     * @param code 任务号
     */
    private void schedule(Runnable runnable, int code) {
        // 创建一个任务，立即执行，每隔model.getSensitivity()再触发一次
        // 例如： 按照 S 不放， 先立即向下一次，然后每隔一段时间向下一次
        executor.scheduleAtFixedRate(new Runnable() {
            private final int myCode = code;

            @Override
            public void run() {
                // keyHold.get() 如果还按着没放手
                // myCode == taskCode.get() 是最新的定时任务
                if (keyHold.get() && myCode == taskCode.get()) {
                    runnable.run();
                } else {
                    // 任务过期了，删掉
                    executor.remove(this);
                }
            }
        }, 0, model.getSensitivity(), TimeUnit.MILLISECONDS);
    }

    /**
     * 创建定时下落的任务
     * @param code 任务号
     * @see MainFrame#schedule(Runnable, int)
     */
    private void scheduleTick(int code) {
        executor.scheduleAtFixedRate(new Runnable() {
            private final int myCode = code;

            @Override
            public void run() {
                if (myCode == tickCode.get()) {
                    model.onTick();
                } else {
                    executor.remove(this);
                }
            }
        }, 0, model.getSpeed(), TimeUnit.MILLISECONDS);
    }

    /**
     * 界面配置
     */
    private void initComponents() {
        this.setSize(700, 670);
        // 不加载标题栏
        this.setUndecorated(true);
        // 允许窗体透明
        AWTUtilities.setWindowOpaque(this, false);

        // 各个组件的初始化
        settingDialog = new SettingDialog(this, model);
        popupMenu = new JPopupMenu();
        //---- menuItemStartGame ----
        menuItemStartGame = new JMenuItem();
        menuItemStartGame.setText("\u5f00\u59cb\u6e38\u620f(F2)");
        menuItemStartGame.addActionListener(e -> {
            model.initMap();
        });
        popupMenu.add(menuItemStartGame);

        //---- menuItemSetting ----
        menuItemSetting = new JMenuItem();
        menuItemSetting.setText("\u8bbe\u7f6e");
        menuItemSetting.addActionListener(e -> {
            // 打开设置菜单
            settingDialog.setVisible(true);
        });
        popupMenu.add(menuItemSetting);

        //---- menuItemExit ----
        menuItemExit = new JMenuItem();
        menuItemExit.setText("\u9000\u51fa");
        menuItemExit.addActionListener(e -> callExit());
        popupMenu.add(menuItemExit);

        // 鼠标事件的处理，用于移动窗口
        this.addMouseListener(new MouseAdapter() {
            int fromX, fromY;
            boolean moving = false;

            /**
             * 右键打开菜单
             */
            @Override
            public void mouseClicked(MouseEvent e) {
                super.mouseClicked(e);
                if (e.getButton() == MouseEvent.BUTTON3) {
                    popupMenu.show(MainFrame.this, e.getX(), e.getY());
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {
//                logger.info("Pressed:" + e.paramString());
                fromX = e.getX();
                fromY = e.getY();
                moving = true;
            }

            /**
             * 似乎没有起作用
             */
            @Override
            public void mouseMoved(MouseEvent e) {
                if (moving) {
                    int newX = MainFrame.this.getX() + e.getX() - fromX;
                    int newY = MainFrame.this.getY() + e.getY() - fromY;
                    MainFrame.this.setLocation(newX, newY);
                }
            }

            @Override
            public void mouseReleased(MouseEvent e) {
//                logger.info("Released:" + e.paramString());
                int newX = MainFrame.this.getX() + e.getX() - fromX;
                int newY = MainFrame.this.getY() + e.getY() - fromY;
                MainFrame.this.setLocation(newX, newY);
                moving = false;
            }
        });

        /**
         * 键盘事件的处理
         */
        this.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                switch (e.getKeyCode()) {
                    case KeyEvent.VK_ESCAPE:
                        // ESC 退出
                        callExit();
                        break;
                    case KeyEvent.VK_F2:
                        // F2 开始新游戏
                        model.initMap();
                        break;
                    case KeyEvent.VK_SPACE:
                        // 空格 暂停、继续
                        model.paused = !model.paused;
                        if (!model.paused) {
                            scheduleTick(tickCode.incrementAndGet());
                        }
                        repaint();
                        break;
                    case KeyEvent.VK_UP:
                    case KeyEvent.VK_W:
                        model.ratoteShape();
                        break;
                    case KeyEvent.VK_DOWN:
                    case KeyEvent.VK_S:
                        keyHold.set(true);
                        schedule(model::moveDown, taskCode.incrementAndGet());
                        break;
                    case KeyEvent.VK_LEFT:
                    case KeyEvent.VK_A:
                        keyHold.set(true);
                        schedule(model::moveLeft, taskCode.incrementAndGet());
                        break;
                    case KeyEvent.VK_RIGHT:
                    case KeyEvent.VK_D:
                        keyHold.set(true);
                        schedule(model::moveRight, taskCode.incrementAndGet());
                        break;
                }
            }

            @Override
            public void keyReleased(KeyEvent e) {
                if (keyHold.get()) {
                    keyHold.set(false);
                }
            }
        });

        // 游戏结束时做什么
        model.addGameOverListener(() -> {
            JOptionPane.showConfirmDialog(MainFrame.this, "Game Over!!!", "Game Over", JOptionPane.OK_OPTION);
            MainFrame.this.repaint();
        });

        // model 变化后做什么
        model.addModelListener((TetrisModel m) -> MainFrame.this.repaint());
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        Graphics2D g2d = (Graphics2D) g;
//        AlphaComposite ac = java.awt.AlphaComposite.getInstance(AlphaComposite.DST_ATOP,0.75f);
//        g2d.setComposite(ac);
        // 画框架背景
        g.drawImage(backImage, 0, 0, 700, 670, TRANSPARENT, this);
        AlphaComposite ac2 = java.awt.AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.75f);
        g2d.setComposite(ac2);

        // 画地图
        for (int i = 0; i < TetrisModel.ROWS; i++) {
            for (int j = 0; j < TetrisModel.COLS; j++) {
                g.drawImage(allBlocks[model.getMap()[i][j]], LEFT_TOP_X + j * BLOCK_SIZE, LEFT_TOP_Y + i * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE, TRANSPARENT, this);
            }
        }

        // 画当前操作的形状
        Shape current = model.getCurrent();
        int shapeColor = model.getShapeColor();
        drawShape(g, current, shapeColor, LEFT_TOP_X, LEFT_TOP_Y, current.getX(), current.getY(), BLOCK_SIZE);

        // 画左上角
        Shape holdShape = model.holds.get(0);
        drawShape(g, holdShape, (shapeColor % 7 + 1), 2, 40, 2, 0, 24);

        // 画右上角
        for (int i = 0; i < 4; i++) {
            Shape shape = model.holds.get(i + 1);
            drawShape(g, shape, (shapeColor + i + 1) % 7 + 1, 472, 40 + 140 * i, 4, 0, 24);
        }

        // 画右下角
        AttributedString attributedString = new AttributedString("" + model.score);
        attributedString.addAttribute(TextAttribute.SIZE, 20);
        attributedString.addAttribute(TextAttribute.FOREGROUND, Color.RED);
        g.drawString(attributedString.getIterator(), 550, 640);

        // 目前并没搞懂AlphaComposite这堆东西，只是想要透明
        AlphaComposite ac3 = java.awt.AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.7f);
        g2d.setComposite(ac3);

        // 画 暂停
        if ((!model.gameOver) && model.paused) {
            g.drawImage(stoppedImage, 250, 200, 210, 210, TRANSPARENT, this);
        }

        // 画 GameOver
        if (model.gameOver) {
            g.drawImage(gameOverImage, 250, 200, 210, 150, new Color(255, 255, 255, 0), this);
        }
    }

    /**
     * 画一个形状到指定位置
     * @param g Graphics
     * @param shape 要画的形状
     * @param color 颜色
     * @param fixedX 固定偏移X
     * @param fixedY 固定偏移Y
     * @param x 以blockSize为单位的偏移x
     * @param y 以blockSize为单位的偏移y
     * @param blockSize 方块的边长
     */
    private void drawShape(Graphics g, Shape shape, int color, int fixedX, int fixedY, int x, int y, int blockSize) {
        int[][] shapeMap = shape.getShape();
        for (int i = 0; i < shapeMap.length; i++) {
            for (int j = 0; j < shapeMap[i].length; j++) {
                int k = shapeMap[i][j];
                if (k > 0) {
                    g.drawImage(allBlocks[color],
                            fixedX + (x + j) * blockSize,
                            fixedY + (y + i) * blockSize,
                            blockSize, blockSize, TRANSPARENT, this);
                }
            }
        }
    }

    /**
     * 退出
     */
    private void callExit() {
        int result = JOptionPane.showConfirmDialog(this, "确认退出？", "退出", JOptionPane.OK_CANCEL_OPTION);
        if (result == JOptionPane.OK_OPTION) {
            System.exit(0);
        }
    }
}
